package com.github.leecho.spring.cloud.gateway.dubbo.client;

import com.github.leecho.spring.cloud.gateway.dubbo.route.DubboRoute;
import com.github.leecho.spring.cloud.gateway.dubbo.route.DubboRoute.DubboProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @author LIQIU
 * @date 2021/6/30 14:55
 */
@Slf4j
public class DefaultDubboClient implements DubboClient {

	private final DubboGenericServiceCache dubboGenericServiceCache;

	private final DubboGenericServiceFactory dubboGenericServiceFactory;

	/**
	 * 异步执行配置
	 */
	private boolean invokeAsync;


	public DefaultDubboClient(DubboGenericServiceCache dubboGenericServiceCache,
							  DubboGenericServiceFactory dubboGenericServiceFactory) {
		this.dubboGenericServiceCache = dubboGenericServiceCache;
		this.dubboGenericServiceFactory = dubboGenericServiceFactory;
	}

	@Override
	public Mono<Object> invoke(DubboRoute route, Map<String, Object> parameters) {
		try {
			if (log.isDebugEnabled()) {
				log.debug("Invoke dubbo service:{} , method:{}, parameters:{}", route.getDubboProvider(), route.getMethod(), parameters);
			}
			GenericService genericService = this.loadGenericService(route.getDubboProvider());
			if (invokeAsync) {
				CompletableFuture<Object> result = genericService.$invokeAsync(route.getMethod(), route.getParameterTypes(), parameters.values().toArray(new Object[]{}));
				if (log.isDebugEnabled()) {
					log.debug("Async invoke dubbo succeed, service:{} , method:{}, parameters:{}", route.getDubboProvider(), route.getMethod(), parameters);
				}
				return Mono.fromFuture(result);
			} else {
				Object result = genericService.$invoke(route.getMethod(), route.getParameterTypes(), parameters.values().toArray(new Object[]{}));
				if (log.isDebugEnabled()) {
					log.debug("Invoke dubbo succeed service:{}, method:{}, parameters:{},result:{}", route.getDubboProvider(), route.getMethod(), parameters, result);
				}
				return Mono.just(result);
			}
		} catch (Throwable e) {
			log.error("Invoke dubbo fail:", e);
			throw new RpcException(e.getMessage(), e);
		}
	}

	/**
	 * 获取Dubbo泛化调用服务
	 *
	 * @param dubboProvider
	 * @return
	 */
	private GenericService loadGenericService(DubboProvider dubboProvider) {
		try {
			GenericService genericService;
			if (this.dubboGenericServiceCache != null) {
				ReferenceBean<GenericService> referenceBean = this.dubboGenericServiceCache.get(dubboProvider);
				if (referenceBean == null) {
					referenceBean = this.dubboGenericServiceFactory.build(dubboProvider);
					referenceBean = dubboGenericServiceCache.put(dubboProvider, referenceBean);
				}
				genericService = referenceBean.get();
			} else {
				return this.dubboGenericServiceFactory.build(dubboProvider).get();
			}
			return genericService;
		} catch (Exception e) {
			throw new RpcException("load dubbo genericService fail", e);
		}
	}

	public void setInvokeAsync(boolean invokeAsync) {
		this.invokeAsync = invokeAsync;
	}


}
